<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  
  
  <title>Linux多线程服务端编程 | Daidaini的 个人博客</title>
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <meta name="description" content="1 当一个对象能被多个线程同时看到时，那么这个对象的销毁时机就会变得模糊不清，可能出现多种竞态条件：  在即将析构一个对象时，从何而知此刻是否有其他线程正在执行该对象的成员函数 如何保证在执行成员函数期间，对象不会在另一个线程被析构 在调用某个对象的成员函数之前，如何得知这个对象还活着？它的析构函数会不会碰巧执行到一半？  解决这些race condtion是C++多线程编程面临的基本问题。可以使">
<meta property="og:type" content="article">
<meta property="og:title" content="Linux多线程服务端编程">
<meta property="og:url" content="https://daidaini.giteee.io/selfblog/2022/03/23/%E3%80%8ALinux%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%BC%96%E7%A8%8B%E3%80%8B%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/index.html">
<meta property="og:site_name" content="Daidaini的 个人博客">
<meta property="og:description" content="1 当一个对象能被多个线程同时看到时，那么这个对象的销毁时机就会变得模糊不清，可能出现多种竞态条件：  在即将析构一个对象时，从何而知此刻是否有其他线程正在执行该对象的成员函数 如何保证在执行成员函数期间，对象不会在另一个线程被析构 在调用某个对象的成员函数之前，如何得知这个对象还活着？它的析构函数会不会碰巧执行到一半？  解决这些race condtion是C++多线程编程面临的基本问题。可以使">
<meta property="og:locale" content="zh_CN">
<meta property="article:published_time" content="2022-03-23T12:50:22.000Z">
<meta property="article:modified_time" content="2023-02-01T01:52:26.934Z">
<meta property="article:author" content="Daidaini">
<meta property="article:tag" content="笔记">
<meta name="twitter:card" content="summary">
  
    <link rel="alternate" href="/selfblog/atom.xml" title="Daidaini的 个人博客" type="application/atom+xml">
  
  
    <link rel="shortcut icon" href="/selfblog/favicon.png">
  
  
    
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/typeface-source-code-pro@0.0.71/index.min.css">

  
  
<link rel="stylesheet" href="/selfblog/css/style.css">

  
    
<link rel="stylesheet" href="/selfblog/fancybox/jquery.fancybox.min.css">

  
<meta name="generator" content="Hexo 6.3.0"></head>

<body>
  <div id="container">
    <div id="wrap">
      <header id="header">
  <div id="banner"></div>
  <div id="header-outer" class="outer">
    <div id="header-title" class="inner">
      <h1 id="logo-wrap">
        <a href="/selfblog/" id="logo">Daidaini的 个人博客</a>
      </h1>
      
    </div>
    <div id="header-inner" class="inner">
      <nav id="main-nav">
        <a id="main-nav-toggle" class="nav-icon"></a>
        
          <a class="main-nav-link" href="/selfblog/">Home</a>
        
          <a class="main-nav-link" href="/selfblog/archives">Archives</a>
        
      </nav>
      <nav id="sub-nav">
        
          <a id="nav-rss-link" class="nav-icon" href="/selfblog/atom.xml" title="RSS 订阅"></a>
        
        <a id="nav-search-btn" class="nav-icon" title="搜索"></a>
      </nav>
      <div id="search-form-wrap">
        <form action="//google.com/search" method="get" accept-charset="UTF-8" class="search-form"><input type="search" name="q" class="search-form-input" placeholder="搜索"><button type="submit" class="search-form-submit">&#xF002;</button><input type="hidden" name="sitesearch" value="https://daidaini.giteee.io/selfblog"></form>
      </div>
    </div>
  </div>
</header>

      <div class="outer">
        <section id="main"><article id="post-《Linux多线程服务端编程》阅读笔记" class="h-entry article article-type-post" itemprop="blogPost" itemscope itemtype="https://schema.org/BlogPosting">
  <div class="article-meta">
    <a href="/selfblog/2022/03/23/%E3%80%8ALinux%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%BC%96%E7%A8%8B%E3%80%8B%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/" class="article-date">
  <time class="dt-published" datetime="2022-03-23T12:50:22.000Z" itemprop="datePublished">2022-03-23</time>
</a>
    
  <div class="article-category">
    <a class="article-category-link" href="/selfblog/categories/C-%E7%BC%96%E7%A8%8B/">C++编程</a>
  </div>

  </div>
  <div class="article-inner">
    
    
      <header class="article-header">
        
  
    <h1 class="p-name article-title" itemprop="headline name">
      Linux多线程服务端编程
    </h1>
  

      </header>
    
    <div class="e-content article-entry" itemprop="articleBody">
      
        <p>1 当一个对象能被多个线程同时看到时，那么这个对象的销毁时机就会变得模糊不清，可能出现多种竞态条件：</p>
<ul>
<li>在即将析构一个对象时，从何而知此刻是否有其他线程正在执行该对象的成员函数</li>
<li>如何保证在执行成员函数期间，对象不会在另一个线程被析构</li>
<li>在调用某个对象的成员函数之前，如何得知这个对象还活着？它的析构函数会不会碰巧执行到一半？</li>
</ul>
<p>解决这些race condtion是C++多线程编程面临的基本问题。可以使用shared_ptr来一劳永逸地解决这些问题。</p>
<p>2 对象构造要做到线程安全，唯一的要求是构造期间不能泄漏this指针。</p>
<p>基于此，二段式构造–即构造函数 + initialize() – 有时会是好办法。这种方式虽然不符合C++教条，但是多线程下别无选择。</p>
<p>3 mutex不能安全地保护析构，因为一旦执行析构，mutex对象也会被销毁。这样，在多线程的情况下，如果其他线程正在使用mutex用到一半，就会有问题。</p>
<p>4 空悬指针：</p>
<p>两个指针p1，p2，指向堆里的同一个对象Object，并且p1和p2位于不同的线程中（线程A和线程B）。假设线程A通过p1指针将对象销毁了，那么p2就成了悬空指针。这是一种典型的C&#x2F;C++内存错误。</p>
<p>要想安全地销毁对象，最好在别人(即别的线程)都看不到的情况下，偷偷地做。这个也正是垃圾回收(gc)的原理，所有人用不到的一定是垃圾。</p>
<p>（悬空指针，是指指向的内容已经被释放的指针）</p>
<p>5 C++里可能出现的内存问题大致有如下几个方面：</p>
<ul>
<li>缓冲区溢出 (buffer overrun)</li>
<li>空悬指针 &#x2F; 野指针</li>
<li>重复释放 (double delete)</li>
<li>内存泄漏 (memory leak)</li>
<li>不配对的 new[] &#x2F; delete</li>
<li>内存碎片 (memory fragment)</li>
</ul>
<p>而正确地使用智能指针可以很轻易地解决前5种问题</p>
<p>6 shared_ptr的拷贝开销要比原始指针的拷贝开销要高（因为拷贝的时候需要修改引用计数，而修改引用计数需要加锁操作）。所以，我们在将shared_ptr作为函数参数传递的时候，尽量使用常引用的形式，这样减少拷贝次数，来减少性能损失。</p>
<p>7 让this指针，能变身为shared_ptr的方法，是让类继承 enable_shared_from_this。</p>
<p>8 弱回调：如果对象还活着，就调用它的成员函数，否则忽略之。</p>
<p>9 read-copy-update</p>
<p>10 不推荐使用信用量(Semaphore)，原因：</p>
<ul>
<li>条件变量配合互斥量可以完全替代其功能，而且更不易出错</li>
<li>semaphore has no notion of ownership</li>
<li>信号量有自己的计数值，而通常我们自己的数据结构也有长度值，这就造成了同样的信息存放了两份，需要时刻保持一致，这增加了程序员的负担和出错的可能。</li>
<li>如果要控制并发度，可以考虑用muduo::ThreadPool</li>
</ul>
<p>11 使用 pthread_once 来实现 Singleton</p>
<p>12 在”non-blocing IO + IO multiplexing” 这种模型（即Reactor模式）中，程序的基本结构是一个事件循环(event loop)， 以事件驱动(event-driven) 和事件回调的方式实现业务逻辑。</p>
<p>Reactor摸型的优点：</p>
<p>编程不难，效率也不错。不仅可以用于读写socket，连接的建立，甚至DNS解析都可以用非阻塞的方式进行，以提高并发度和吞吐量，对于IO密集的应用是一个不错的选择。</p>
<p>缺点：</p>
<p>它要求事件回调函数必须是非阻塞的。对于涉及网络IO的请求响应式协议，它容易割裂业务逻辑，使其散布于多个回调函数之中，相对不容易理解和维护。</p>
<p>13 one loop per thread</p>
<p>libev的作者说：</p>
<p>One loop per thread is usually a good model. Doing this is almost never wrong, sometimes a better-performance model exists, but it is always a good start.</p>
<p>这种方式的好处，在于：</p>
<ul>
<li>线程数目基本固定，可以在程序启动的时候设置，不会频繁创建与销毁</li>
<li>可以很方便地在线程之间调配负载</li>
<li>IO事件发生的线程是固定的，同一个TCP连接不必考虑事件并发</li>
</ul>
<p>Eventloop代表了线程的主循环，需要让哪个线程干活，就把timer或IOchannel(如TCP连接)注册到哪个线程的loop里即可。</p>
<p>对实时性有要求的额connection 可以单独用一个线程；</p>
<p>数据量大的connection可以独占一个线程，并把数据处理任务分摊到另几个计算线程中（用线程池）；</p>
<p>其他次要的辅助性connection可以共享一个线程。</p>
<p>对于具有一定规模的服务端程序，一般就会采用 non-blockong + IO multiplexing， 每个connection&#x2F;acceptor 都会注册到某个eventloop上，程序里有多个event loop,每个线程至多有一个event loop。</p>
<p>多线程程序对event loop 提出了更高的要求，那就是“线程安全”。要允许一个线程往别的线程的loop里塞数据，这个loop必须得是线程安全的。</p>
<p>14 进程间通信首选Sockets(主要是指TCP)，其最大的好处在于：可以跨主机，具有伸缩性。其他优势：</p>
<ul>
<li>在编程上，TCP sockets和pipe都是操作文件描述符，用来收发字节流，都可以 read&#x2F;write&#x2F;fcntl&#x2F;poll等。不同的是，TCP是双向的；Linux的pipe是单向的，使用没有TCP方便</li>
<li>TCP的port是由一个进程独占的，而且操作系会自动回收(listening port 和已建立连接的TCP socket都是文件描述符，在进程结束时操作系统会自动关闭所有文件描述符)。这说明，即使程序意外退出，也不会给系统留下垃圾，程序重启之后就可以比较容易地恢复，而不需要重启操作系统。还有一个好处，既然port是独占的，就可以防止程序重复启动。</li>
<li>两个进程通过TCP通信，如果一个崩溃了，操作系统会关闭连接，另一个进程就会立刻感知到，可以快速failover（故障转移）。</li>
<li>与其他IPC相比，TCP的一个天生的好处是“可记录、可重现”。tcpdump和Wireshark 是解决两个进程间协议和状态争端的好帮手，也是性能(吞吐量、延迟等)分析的利器。我们可以借此编写分布式程序的自动化回归测试。还可以用tcpcopy之类的工具进行压力测试。</li>
<li>TCP还能跨语言，服务端和客户端之间可以不必使用同一种语言</li>
<li>使用TCP这种字节流方式通信，会有 marshal&#x2F;unmarshal的开销，这就要求我们选用合适的消息格式，准确的说是 wire format(字节序列？)，推荐用 Google Protocol Buffers</li>
</ul>
<p>15 使用TCP长连接的好处有两点：</p>
<ul>
<li>容易定位分布式系统中的服务之间的依赖关系。只要在机器上运行 netstat -tpna | grep :port 就能立刻列出用到某服务的客户端地址，然后在客户端的机器上用 netstat 或者 lsof 命令找出是哪个进程发起的连接。</li>
<li>通过接收和发送队列的长度也比较容易定位网络或者程序故障。</li>
</ul>
<p>16 本书对 “服务器开发” 的定义，用一句话形容：</p>
<p>跑在多核机器上的Linux用户态的没有用户界面的长期运行的 网络应用程序，通常是分布式系统的组成部件。</p>
<p>17 多线程的适用场景时：提高响应速度，让IO和“计算”相互重叠，降低latency(延迟)。虽然多线程不能提高绝对性能，但是能提高平均响应性能。</p>
<p>一个程序要做成多线程的，大致要满足：</p>
<ul>
<li>有多个CPU可用。单核机器上多线程没有性能优势(但或许能简化并发业务逻辑的实现)</li>
<li>线程间有共享数据，即内存中的全局状态。</li>
<li>共享的数据是可以修改的，而不是静态常量表。</li>
<li>提供非均质的服务。即，事件的响应有优先级差异，我们可以用专门的线程来处理优先级高的事件。防止优先级反转。</li>
<li>latency 和 throuthput 同样重要，不是逻辑简单的 IO密集或是 CPU密集。换言之，程序是有相当的计算量的</li>
<li>能scale up(有规模的增长)。 一个好的多线程程序应该能享受增加CPU数目带来的好处，一旦CPU从8核升级到16核，程序能体现出这种升级带来的性能提升</li>
<li>具有可预测的性能。随着负载增加，性能缓慢下降，超过某个临界点之后会极速下降。线程数目一般不随负载变化。</li>
<li>多线程能有效地划分责任与功能，让每个线程的逻辑比较简单，任务单一，便于编码。而不是所有逻辑塞到一个event loop中，导致不同类别的事件之间相互影响</li>
</ul>
<p>18 多线程服务程序中的线程大致可以分为三类：</p>
<ul>
<li>IO线程，这类线程的主循环是IO multiplexing，阻塞地等在 select&#x2F;poll&#x2F;epoll_wait 等系统调用上。这类线程也处理定时事件。当然，它的功能不光光是IO，有些简单的计算也可以放入其中，比如消息的编码或者解码等。</li>
<li>计算线程，这类线程的主循环是 blocingqueue， 阻塞地等在 conditionvariable上。这类线程一般位于 thread pool中。这种线程一般不涉及IO，一般要避免任何阻塞操作</li>
<li>第三方库所用的线程，比如 logging ，又比如database connection等</li>
</ul>
<p>19 Linux 能同时启动多少个线程？</p>
<p>对于32-bit Linux，一个进程的地址空间是4GB，其中用户态能访问的为3GB左右，而一个线程的默认栈大小是10 MB，简单计算，一个进程大约可以同时启动300个线程。</p>
<p>对于64-bit系统，线程数目可大大增加</p>
<p>20 尽管C++03标准没有明说标准库的线程安全性，但</p>
<ul>
<li>我们可以遵循一个基本原则：凡是非共享的对象都是彼此独立的，如果一个对象从始至终只被一个线程使用，那么它就是安全的。</li>
<li>另外一个事实标准是：共享的对象的read-only操作是安全的，前提是不能有并发的写操作。</li>
</ul>

      
    </div>
    <footer class="article-footer">
      <a data-url="https://daidaini.giteee.io/selfblog/2022/03/23/%E3%80%8ALinux%E5%A4%9A%E7%BA%BF%E7%A8%8B%E6%9C%8D%E5%8A%A1%E7%AB%AF%E7%BC%96%E7%A8%8B%E3%80%8B%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/" data-id="cldmi24ln0003yhs1e51v21ag" data-title="Linux多线程服务端编程" class="article-share-link">分享</a>
      
      
      
  <ul class="article-tag-list" itemprop="keywords"><li class="article-tag-list-item"><a class="article-tag-list-link" href="/selfblog/tags/%E7%AC%94%E8%AE%B0/" rel="tag">笔记</a></li></ul>

    </footer>
  </div>
  
    
<nav id="article-nav">
  
    <a href="/selfblog/2022/03/23/%E5%B8%B8%E7%94%A8Linux%E5%91%BD%E4%BB%A4/" id="article-nav-newer" class="article-nav-link-wrap">
      <strong class="article-nav-caption">前一篇</strong>
      <div class="article-nav-title">
        
          常用Linux命令
        
      </div>
    </a>
  
  
    <a href="/selfblog/2022/03/23/%E5%88%9D%E5%A7%8B%E5%8C%96%E5%88%97%E8%A1%A8/" id="article-nav-older" class="article-nav-link-wrap">
      <strong class="article-nav-caption">后一篇</strong>
      <div class="article-nav-title">初始化列表</div>
    </a>
  
</nav>

  
</article>


</section>
        
          <aside id="sidebar">
  
    
  <div class="widget-wrap">
    <h3 class="widget-title">分类</h3>
    <div class="widget">
      <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/selfblog/categories/C-11/">C++11</a></li><li class="category-list-item"><a class="category-list-link" href="/selfblog/categories/C-%E7%BC%96%E7%A8%8B/">C++编程</a></li><li class="category-list-item"><a class="category-list-link" href="/selfblog/categories/Linux%E7%B3%BB%E7%BB%9F/">Linux系统</a></li><li class="category-list-item"><a class="category-list-link" href="/selfblog/categories/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/">操作系统</a></li><li class="category-list-item"><a class="category-list-link" href="/selfblog/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/">数据结构与算法</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">标签</h3>
    <div class="widget">
      <ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/selfblog/tags/C-%E5%AD%A6%E4%B9%A0/" rel="tag">C++学习</a></li><li class="tag-list-item"><a class="tag-list-link" href="/selfblog/tags/C-%E6%A0%87%E5%87%86%E5%BA%93/" rel="tag">C++标准库</a></li><li class="tag-list-item"><a class="tag-list-link" href="/selfblog/tags/C-%E7%89%B9%E6%80%A7/" rel="tag">C++特性</a></li><li class="tag-list-item"><a class="tag-list-link" href="/selfblog/tags/C%E8%AF%AD%E8%A8%80/" rel="tag">C语言</a></li><li class="tag-list-item"><a class="tag-list-link" href="/selfblog/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/" rel="tag">操作系统</a></li><li class="tag-list-item"><a class="tag-list-link" href="/selfblog/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/" rel="tag">数据结构与算法</a></li><li class="tag-list-item"><a class="tag-list-link" href="/selfblog/tags/%E7%AC%94%E8%AE%B0/" rel="tag">笔记</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">标签云</h3>
    <div class="widget tagcloud">
      <a href="/selfblog/tags/C-%E5%AD%A6%E4%B9%A0/" style="font-size: 10px;">C++学习</a> <a href="/selfblog/tags/C-%E6%A0%87%E5%87%86%E5%BA%93/" style="font-size: 20px;">C++标准库</a> <a href="/selfblog/tags/C-%E7%89%B9%E6%80%A7/" style="font-size: 20px;">C++特性</a> <a href="/selfblog/tags/C%E8%AF%AD%E8%A8%80/" style="font-size: 10px;">C语言</a> <a href="/selfblog/tags/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/" style="font-size: 10px;">操作系统</a> <a href="/selfblog/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95/" style="font-size: 10px;">数据结构与算法</a> <a href="/selfblog/tags/%E7%AC%94%E8%AE%B0/" style="font-size: 20px;">笔记</a>
    </div>
  </div>

  
    
  <div class="widget-wrap">
    <h3 class="widget-title">归档</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/selfblog/archives/2023/02/">二月 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/selfblog/archives/2023/01/">一月 2023</a></li><li class="archive-list-item"><a class="archive-list-link" href="/selfblog/archives/2022/03/">三月 2022</a></li><li class="archive-list-item"><a class="archive-list-link" href="/selfblog/archives/2021/09/">九月 2021</a></li><li class="archive-list-item"><a class="archive-list-link" href="/selfblog/archives/2021/07/">七月 2021</a></li></ul>
    </div>
  </div>


  
    
  <div class="widget-wrap">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget">
      <ul>
        
          <li>
            <a href="/selfblog/2023/02/09/%E5%9B%9E%E8%B0%83%E5%87%BD%E6%95%B0%E7%9A%84%E7%90%86%E8%A7%A3/">回调函数的理解</a>
          </li>
        
          <li>
            <a href="/selfblog/2023/02/07/%E4%BA%8C%E5%88%86%E6%B3%95%E7%9A%84%E7%90%86%E8%A7%A3/">二分法的理解</a>
          </li>
        
          <li>
            <a href="/selfblog/2023/01/31/%E6%8C%87%E9%92%88%E7%9A%84%E7%90%86%E8%A7%A3/">C指针的理解</a>
          </li>
        
          <li>
            <a href="/selfblog/2023/01/31/%E7%90%86%E8%A7%A3CPP%E7%9A%84%E7%A7%BB%E5%8A%A8%E8%AF%AD%E4%B9%89/">理解CPP的移动语义</a>
          </li>
        
          <li>
            <a href="/selfblog/2022/03/23/%E5%B8%B8%E7%94%A8Linux%E5%91%BD%E4%BB%A4/">常用Linux命令</a>
          </li>
        
      </ul>
    </div>
  </div>

  
</aside>
        
      </div>
      <footer id="footer">
  
  <div class="outer">
    <div id="footer-info" class="inner">
      
      &copy; 2023 Daidaini<br>
      Powered by <a href="https://hexo.io/" target="_blank">Hexo</a>
    </div>
  </div>
</footer>

    </div>
    <nav id="mobile-nav">
  
    <a href="/selfblog/" class="mobile-nav-link">Home</a>
  
    <a href="/selfblog/archives" class="mobile-nav-link">Archives</a>
  
</nav>
    


<script src="/selfblog/js/jquery-3.4.1.min.js"></script>



  
<script src="/selfblog/fancybox/jquery.fancybox.min.js"></script>




<script src="/selfblog/js/script.js"></script>





  </div>
</body>
</html>